from copy import deepcopy
from math import floor
class Brain:
    def __init__(self):
        self.rooms = list()
        self.rooms.append(Room("T13", 1))
        self.rooms.append(Room("T14", 1))
        self.rooms.append(Room("T15", 1))
        self.rooms.append(Room("T16", 1))
        self.rooms.append(Room("T11", 2))
        self.rooms.append(Room("T19", 2))
        self.rooms.append(Room("T20", 2))
        self.rooms.append(Room("T12", 2))
        self.rooms.append(Room("T10", 3))


        # Ranks = 4 (head), 3 (professor), 2 (research), 1 (PhD student)
        self.persons = list()
        self.persons.append(Person('A', False, True, 4)) #A

        self.persons.append(Person('B', False, True, 3)) #B
        self.persons.append(Person('C', False, False, 3)) #C
        self.persons.append(Person('D', True, True, 3)) #D

        self.persons.append(Person('E', True, True, 2)) #E
        self.persons.append(Person('F', False, False, 2)) #F
        self.persons.append(Person('G', False, False, 2)) #G

        self.persons.append(Person('H', False, True, 1)) #H
        self.persons.append(Person('I', True, False, 1)) #I
        self.persons.append(Person('J', True, False, 1)) #J
        self.persons.append(Person('K', False, False, 1)) #K


        print(self.assign2(self.rooms,self.persons,Room("",0),Person("",0,0,0),0))
        print(self.assign(self.rooms,self.persons,Room("",0),Person("",0,0,0)))

    def constraintEval(self, rooms, persons):
        evaluationResult = dict()
        sum = 0
        for p in persons:
            evaluationResult[p] = set()
            for r in rooms:    
                if (r.size > len(r.occupants)):
                    if self.allowsPerson(p,r):
                        evaluationResult[p].add(r)
                        sum += 1

        person = ""
        acceptableRooms = 1000
        for p in persons:
            if len(evaluationResult[p]) < acceptableRooms and len(evaluationResult[p]) > 0:
                acceptableRooms = len(evaluationResult[p])
                person = p
                rooms = evaluationResult[p]
        return (person, rooms, sum)

    def updateLists(self, rooms, persons, room, person):
        for p in persons:
            if p.ID == person.ID:
                persons.remove(p)
        for r in rooms:
            if r.name == room.name:
                r.add(person)

    def assign2(self,rooms,persons,room,person, test):

        localRooms = deepcopy(rooms)
        localPersons = deepcopy(persons)
        #Remove the person we're working with
        self.updateLists(localRooms, localPersons, room, person)
        #If we've given desks to everyone and we're not working in a test case
        if len(localPersons) == 0 and test == 0:
            for r in localRooms:
                r.toString()
            return True

        (optimalPerson, roomSet, possiblePositions) = self.constraintEval(localRooms,localPersons)
        #If we're in a test case, return the possibilities of taking this route
        if test == 1:
            return possiblePositions

        #If this person can't be assigned anywhere
        if len(roomSet) == 0 or optimalPerson == "":
            return False

        #Check possibilities of all possible allocations of the most constrained variable
        testResults=list()
        for r in roomSet:
            testResults.append( (self.assign2(localRooms,localPersons,r,optimalPerson,1), r) )
        #Find the room with the highest amount of positions
        while(testResults):
            (mostPositions, bestRoom) = (0,0)
            for (positions, r) in testResults:
                if (positions >= mostPositions):
                    (mostPositions, bestRoom) = (positions, r)
            testResults.remove((positions, r))
            if (bestRoom != 0):
                if self.assign2(localRooms, localPersons,bestRoom,optimalPerson,0) == True:
                     return True
        
        return False

               

    def assign(self,rooms,persons,room,person):
        localRooms = deepcopy(rooms)
        localPersons = deepcopy(persons)
        for p in localPersons:
            if p.ID == person.ID:
                localPersons.remove(p)
        for r in localRooms:
            if r.name == room.name:
                r.add(person)
        if len(localPersons) == 0:
            for r in localRooms:
                r.toString()
            return True

        for p in localPersons:
            for r in localRooms:
                if (r.size > len(r.occupants)):
                    if self.allowsPerson(p,r):
                        if self.assign(localRooms, localPersons,r,p) == True:
                            return True

        return False

                


    def allowsPerson(self, person, room):
        if (self.smokerRule(person,room)):
            if (self.visitorRule(person,room)):
                if (self.rankRule(person,room)):
                    if (self.roomSizeRule(person,room)):
                        return True;
    #Only smokers or only none-smoker
    def smokerRule(self, person, room):
        smoker = person.smoker
        for p in room.occupants:
            if (p.smoker != smoker):
                return False
        return True
    
    #only one person with visitors per room
    def visitorRule(self, person, room):
        if person.visitors == 0:
            return True
        else:
            for p in room.occupants:
                if p.visitors == 1:
                    return False
            return True

    #High-rank people should be alone
    def rankRule(self, person, room):
        if len(room.occupants) == 0:
            return True
        elif person.rank in {3,4}:
             return False
        elif person.rank in {1,2}:
            for p in room.occupants:
                if p.rank in {3,4}:
                    return False
            return True

    def roomSizeRule(self,person,room):
        if person.rank in {1,2}:
            return True
        elif person.rank in {3,4}:
            if room.size in {2,3}:
                return True
            return False



class Person:
    smoker = dict()
    smoker[False] = "non-smoker"
    smoker[True] = "smoker"

    visitors = dict()
    visitors[False] = "few visitors"
    visitors[True] = "many visitors"

    rank = dict()
    rank[1] = "PhD student"
    rank[2] = "researcher"
    rank[3] = "professor"
    rank[4] = "head"
    def __init__(self, ID, smoker, visitors, rank):
        self.ID = ID
        self.smoker = smoker
        self.visitors = visitors
        self.rank = rank

    def toString(self):
        return ("%s (%s, %s, %s)" % (self.ID, Person.smoker[self.smoker], Person.visitors[self.visitors], Person.rank[self.rank]))

class Room:
    def __init__(self, name, size):
        self.name = name
        self.size = size
        self.occupants = list()

    def add(self, person):
        self.occupants.append(person)

    def toString(self):
        print("%s: %d desks: %s" % (self.name, self.size, self.occupantsString()))

    def occupantsString(self):
        asd = "\n\t"
        for p in self.occupants:
            asd = asd + p.toString() + '\n\t'
        return asd

B = Brain()



    
